substitutions:
  friendly_name: Coffee Roaster
esphome:
  name: coffee-roaster
  on_boot:
    priority: 500
    then:
      - climate.control:
          id: pid_controller
          mode: "OFF"
          target_temperature: 15.5556°C
      - text_sensor.template.publish:
          id: roast_status
          state: "On"
      - light.turn_off:
          id: neopixel_light

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: "XXXXXXXXXXXXXXXXXXXXXXX"

wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_password
  - ssid: !secret trailer_wifi_ssid
    password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Coffee-Roaster Fallback Hotspot"
    password: !secret wifi_password

#Removed so that when it can't connect to WiFi, the local AP it creates is for the web server and not setting up a different WiFi connection (i.e. allows the web server to be accessed without a WiFi network)
#captive_portal:
    
web_server:
  port: 80
  local: true


interval:
  - interval: 1s
    then:
    
    #Safety check to make sure that if the heater is on, the fan is also on
      - if:
          condition:
            and:            
              - lambda: 'return id(heat_value).state > 0;'
              - fan.is_off: fan_speed
          then:
            - script.stop: roast_1
            - climate.control:
                id: pid_controller
                mode: "OFF"
            - fan.turn_on:
                id: fan_speed
                speed: 100
            - text_sensor.template.publish:
                id: roast_status
                state: "ERROR: Heater On, Fan Not On"
            - light.turn_on:
                id: neopixel_light
                brightness: 100%
                red: 100%
                green: 0%
                blue: 0%
                effect: "Blink"







    #Safety check to make sure that if the heater is on for a while, the temperature actually increases.
      - if:
          condition:
            and:
              - lambda: 'return id(heat_value).state == 100;'
              - lambda: 'return id(heater_counter) == 0;' 
          then:
            - lambda: |-
                id(heater_start_temp) = id(temp_sensor).state; 
            - lambda: |-
                id(heater_counter) = (id(heater_counter) + 1);
          else:
            - if:
                condition:
                  and:
                    - lambda: 'return id(heat_value).state == 100;'
                    - lambda: 'return id(heater_counter) > 0;'
                then:
                  - lambda: |-
                        id(heater_counter) = (id(heater_counter) + 1);
                else:
                  - lambda: |-
                        id(heater_counter) = 0;
                
      - if:
          condition:
            and:
              - lambda: 'return id(heater_counter) > 60;'
              - lambda: 'return id(temp_sensor).state <= (id(heater_start_temp) + 1);'
          then:
            - script.stop: roast_1
            - climate.control:
                id: pid_controller
                mode: "OFF"
            - fan.turn_on:
                id: fan_speed
                speed: 100
            - text_sensor.template.publish:
                id: roast_status
                state: "ERROR: Heater On, Temperature Not Increasing"
            - light.turn_on:
                id: neopixel_light
                brightness: 100%
                red: 100%
                green: 0%
                blue: 0%
                effect: "Blink"
                
          else:
            - if:
                condition:
                  - lambda: 'return id(heater_counter) > 60;'
                then:
                  - lambda: |-
                      id(heater_counter) = 0;
      





number:
  - platform: template
    name: Roast 1 Preheat Temp
    optimistic: true
    id: roast_1_preheat_temp
    icon: "mdi:thermometer"
    unit_of_measurement: °C
    max_value: 270
    min_value: 15
    step: 1
    initial_value: 120
    mode: box

  - platform: template
    name: Roast 1 Temp
    optimistic: true
    id: roast_1_temp
    icon: "mdi:thermometer"
    unit_of_measurement: °C
    max_value: 270
    min_value: 15
    step: 1
    initial_value: 221
    mode: box

  - platform: template
    name: Roast 1 Ramp
    optimistic: true
    id: roast_1_ramp
    icon: "mdi:thermometer-chevron-up"
    unit_of_measurement: °C/min
    max_value: 120
    min_value: 1
    step: 1
    initial_value: 10
    mode: box

  - platform: template
    name: Roast 1 Fan Speed
    optimistic: true
    id: roast_1_fan_speed
    icon: "mdi:fan-speed-1"
    unit_of_measurement: "%"
    max_value: 100
    min_value: 1
    step: 1
    initial_value: 65
    mode: box

button:
  - platform: template
    name: "Start Roast #1"
    id: start_roast_1
    icon: "mdi:coffee"
    on_press:
        then:
            - if:
                condition:
                  lambda: 'return (id(currently_roasting) == 1);'
                then:
                else:
                    - script.execute: roast_1


globals:
    - id: currently_roasting
      type: int
      restore_value: no
      initial_value: '0'

    - id: current_temp_setpoint
      type: int
      restore_value: no
      initial_value: '0'
     
    #Used for heater safety interval check
    - id: heater_counter
      type: int
      restore_value: no
      initial_value: '0'
      
    - id: heater_start_temp
      type: int
      restore_value: no
      initial_value: '0'


script:

  - id: roast_timer
    
    then:
      - delay: 1500s


  - id: roast_1
    
    then:
      - globals.set:
          id: currently_roasting
          value: '1'

      - light.turn_on:
          id: button_light_switch_1
        
      - light.turn_off:
          id: button_light_switch_2
        
      - light.turn_off:
          id: button_light_switch_3
        
      - light.turn_on:
          id: neopixel_light
          brightness: 50%
          red: 100%
          green: 100%
          blue: 0%
        
      - fan.turn_on:
          id: fan_speed
          speed: !lambda |-
                                return id(roast_1_fan_speed).state;
        
      - climate.control:
         id: pid_controller
         mode: HEAT
         target_temperature: !lambda |-
                                return id(roast_1_preheat_temp).state;
      
      - text_sensor.template.publish:
          id: roast_status
          state: "Roast #1 Preheat"
        
      - wait_until:
          condition:
            lambda: |-
              return id(temp_sensor).state > (id(roast_1_preheat_temp).state - 1);

          timeout: 300s
            
      - if:
          condition:
            - lambda: |-
                return id(temp_sensor).state < (id(roast_1_preheat_temp).state - 1);
                  
          then:
            - script.stop: roast_1
            - climate.control:
                id: pid_controller
                mode: "OFF"
            - fan.turn_on:
                id: fan_speed
                speed: 100
            - text_sensor.template.publish:
                id: roast_status
                state: "ERROR: Preheat Timeout"
            - light.turn_on:
                id: neopixel_light
                brightness: 100%
                red: 100%
                green: 0%
                blue: 0%
                effect: "Blink"
                  
          else:      
      
      - delay: 120s
        
      - light.turn_on:
          id: neopixel_light
          brightness: 50%
          red: 100%
          green: 0%
          blue: 0%
        
      - globals.set:
                  id: current_temp_setpoint
                  value: !lambda |-
                            return id(roast_1_preheat_temp).state;
      
      - script.execute: roast_timer
      
      - text_sensor.template.publish:
          id: roast_status
          state: "Roast #1 Ramp-up"
      
      - while:
          condition:
            and:
              - script.is_running: roast_timer
              - lambda: |-
                  return id(temp_sensor).state < id(roast_1_temp).state;
            
          then:
                
              - climate.control:
                  id: pid_controller
                  mode: HEAT
                  target_temperature: !lambda |-
                      return id(current_temp_setpoint);
                
              # delay will be calculated by dividing 60 by C/min value
              - delay: !lambda |-
                            return 60000 / id(roast_1_ramp).state;
                
              - if:
                  condition:
                    - lambda: |-
                        return id(current_temp_setpoint) < id(roast_1_temp).state;
                  
                  then:
                    - lambda: |-
                        id(current_temp_setpoint) = (id(current_temp_setpoint) + 1);
                  
                  else:

#Removed since I don't plan to change the fan speed during a roast                                         
#      - fan.turn_on:
#          id: fan_speed
#          speed: 100

      - light.turn_on:
          id: neopixel_light
          brightness: 50%
          red: 100%
          green: 65%
          blue: 0%
        
      - climate.control:
          id: pid_controller
          mode: "OFF"
          target_temperature: 15.5556°C
      
      - text_sensor.template.publish:
          id: roast_status
          state: "Roast #1 Cooldown"
      
      - wait_until:
          condition:
              sensor.in_range:
                  id: temp_sensor
                  below: 35
          timeout: 1200s
        
      - fan.turn_off: fan_speed

      - globals.set:
          id: currently_roasting
          value: '0'
      
      - text_sensor.template.publish:
          id: roast_status
          state: "Roast #1 Done"
      
      - light.turn_on:
          id: neopixel_light
          brightness: 50%
          red: 0%
          green: 100%
          blue: 0%

  - id: blank_script
    
    then:


light:

  - platform: binary
    name: $friendly_name Button Light 1
    output: button_light_1
    id: button_light_switch_1
    restore_mode: ALWAYS_OFF

  - platform: binary
    name: $friendly_name Button Light 2
    output: button_light_2
    id: button_light_switch_2
    restore_mode: ALWAYS_OFF

  - platform: binary
    name: $friendly_name Button Light 3
    output: button_light_3   
    id: button_light_switch_3
    restore_mode: ALWAYS_OFF
    
  - platform: neopixelbus
    type: RGB
    variant: SK6812
    pin: GPIO22
    num_leds: 1
    name: $friendly_name NeoPixel
    id: neopixel_light
    restore_mode: ALWAYS_OFF
    default_transition_length: 0s
    effects:
      - pulse:
          name: "Blink"
          transition_length: 0s
          update_interval: 1s


binary_sensor:
  - platform: gpio
    pin:
        number: GPIO27
        inverted: true
        mode:
            input: true
            pullup: true
    name: $friendly_name Button 1
    on_press:
        then:
            - if:
                condition:
                  lambda: 'return (id(currently_roasting) == 1);'
                then:
                else:
                    - script.execute: roast_1

  - platform: gpio
    pin:
        number: GPIO32
        inverted: true
        mode:
            input: true
            pullup: true
    name: $friendly_name Button 2

  - platform: gpio
    pin:
        number: GPIO33
        inverted: true
        mode:
            input: true
            pullup: true
    name: $friendly_name Button 3


output:


  - id: button_light_1
    platform: gpio
    pin: GPIO4

  - id: button_light_2
    platform: gpio
    pin: GPIO25
    
  - id: button_light_3
    platform: gpio
    pin: GPIO26


  - platform: ledc
    pin: GPIO16
    frequency: 1000 Hz #might need to change to get it to work?
    id: fan_pwm
    min_power: 0.2
    zero_means_zero: true

  - platform: slow_pwm
    pin: GPIO17
    id: heater_pwm
    period: 15s
    turn_on_action:
      - fan.turn_on:
          id: fan_speed


    
    #Required this action and leaving it blank threw errors, so I just made a script to call that does nothing.
    turn_off_action:
      - script.execute: blank_script
    

fan:
  - platform: speed
    output: fan_pwm
    name: $friendly_name Fan
    id: fan_speed
    restore_mode: ALWAYS_OFF

#  - platform: speed
#    output: heater_pwm
#    name: $friendly_name Heater
#    icon: mdi:heating-coil
#    restore_mode: ALWAYS_OFF

climate:
  - platform: pid
    name: $friendly_name PID Controller
    id: pid_controller
    sensor: temp_sensor
    default_target_temperature: 15.5556°C
    heat_output: heater_pwm
    control_parameters:
      kp: 0.05009
      ki: 0.00230
      kd: 0.27302
    visual:
      min_temperature: 15.5556°C
      max_temperature: 270°C
      temperature_step: 1°C
    

switch:
  - platform: template
    name: $friendly_name PID Controller Autotune
    turn_on_action:
      - climate.pid.autotune: pid_controller

spi:
  clk_pin: GPIO14 #14 #18
  mosi_pin: GPIO13 #13 #23
  miso_pin: GPIO12 #12 #19

sensor:
  - platform: max31855
    name: $friendly_name Temperature
    cs_pin: GPIO15 #15 #5
    update_interval: 1s
    #filters:
    #  - lambda: return x * (9.0/5.0) + 32.0;
    #unit_of_measurement: "°F"
    id: temp_sensor
    on_value_range:
      - above: 280
        then:
          - script.stop: roast_1
          - climate.control:
              id: pid_controller
              mode: "OFF"
          - fan.turn_on:
              id: fan_speed
              speed: 100
          - text_sensor.template.publish:
              id: roast_status
              state: "ERROR: OVERHEAT PROTECTION"
          - light.turn_on:
                id: neopixel_light
                brightness: 100%
                red: 100%
                green: 0%
                blue: 0%
                effect: "Blink"

  - platform: wifi_signal
    name: $friendly_name WiFi Strength
    update_interval: 60s
  - platform: uptime
    name: $friendly_name Uptime

  - platform: pid
    name: $friendly_name PID Kp
    type: KP
  - platform: pid
    name: $friendly_name PID Ki
    type: KI
  - platform: pid
    name: $friendly_name PID Kd
    type: KD
  - platform: pid
    name: $friendly_name PID Heat
    type: HEAT
    id: heat_value


text_sensor:
  - platform: wifi_info
    ip_address:
      name: $friendly_name IP Address
  
  - platform: template
    name: $friendly_name Status
    id: roast_status
    icon: "mdi:card-text"
    

status_led:
  pin:
    number: GPIO2
    inverted: false